/*
	Copyright (c) 2010 <copyright holders>
	This source code is licensed under The MIT License,
	see the file LICENSE for details.
	*/

#include "UnitInterpolator.hpp"

#include "PresentationDefs.hpp"
#include "UnitInvariant.hpp"

namespace spelprog2 {
namespace presentation {
UnitState::UnitState():
    weight(0),
    blendOutTime(0.1f),
    blendInTime(0.1f),
    active(false),
    disabled(true),
    age(0)
{}

float UnitState::getNormalizedWeight(UnitInterpolator *ui)
{
    float sum = ui->stateWeightSum();
    if(sum < 0.000001f) return weight.get();
    return weight.get()/sum;
}

void UnitState::update(float time, UnitInterpolator *ui)
{
    if(!disabled)
    {
        weight.update(time);
        {
            float weight = getNormalizedWeight(ui);
            for(size_t i = 0; i < animations.size(); i++)
            {
                ui->setAnimationWeight(animations[i], weight);
            }
        }
        if(active)
        {
            age += time;
        }
        else
        {
            if( weight.get() == 0 )
            {
                for(size_t i = 0; i < animations.size(); i++)
                {
                    ui->endAnimation(animations[i]);
                }
                animations.clear();
                end(ui);
                disabled = true;
            }
        }
        if(weight.get() > 0)
        {
          goUpdate(time, ui);
        }
    }
}

// Motion state
MotionState::MotionState():
    UnitState(),
    speed(0.0f, 0.2f)
{
    blendInTime = 0.1f;
    blendOutTime = 0.2f;
}

void MotionState::stateUpdate(UnitInterpolator *ui, sim::Unit::ConstPointer unit)
{
    speed.set(ui->getSpeed());
}

void MotionState::goUpdate(float time, UnitInterpolator *ui)
{
    speed.update(time);
        
    float distance = speed.get();
    float walkSpeed = 0.3f;
    float weight = getNormalizedWeight(ui);
    
    ui->setAnimationWeight("Stand", weight * std::max(0.0f, (1 - distance/walkSpeed)));
    ui->setAnimationWeight("Walk", weight * std::min(1.0f, distance/walkSpeed) - std::max(0.0f,
        std::min(1.0f, distance/walkSpeed - 1)));
    ui->setAnimationWeight("Run", weight * std::max(0.0f, std::min(1.0f, distance/walkSpeed - 1)));
}

void MotionState::init(UnitInterpolator *ui)
{
    float startTime = (float)(rand() % 256)/(float)256;
    ui->enableLoopingAnimation("Stand", startTime);
    ui->enableLoopingAnimation("Walk", startTime);
    ui->enableLoopingAnimation("Run", startTime);
}

void MotionState::end(UnitInterpolator *ui)
{
    ui->endAnimation("Stand");
    ui->endAnimation("Walk");
    ui->endAnimation("Run");
}

// Fighting state
FightState::FightState():
    UnitState()
{
    blendInTime = 0.2f;
    blendOutTime = 0.2f;
    startTime = (float)(rand() % 512)/256;
}

void FightState::stateUpdate(UnitInterpolator *ui, sim::Unit::ConstPointer unit)
{
}

void FightState::goUpdate(float time, UnitInterpolator *ui)
{
    float weight = getNormalizedWeight(ui);
    ui->setAnimationWeight("Fight", weight);
    
    if(getAge() > 1.0f)
    {
        disable();
    }
}

void FightState::init(UnitInterpolator *ui)
{
    ui->enableLoopingAnimation("Fight", 0);
    handleAnimation("Fight");
}
void FightState::end(UnitInterpolator *ui)
{
    
}

DeathState::DeathState():
    UnitState()
{
    blendInTime = 0.2f;
}

void DeathState::stateUpdate(UnitInterpolator *ui, sim::Unit::ConstPointer unit)
{
    
}

void DeathState::goUpdate(float time, UnitInterpolator *ui)
{
    float weight = getNormalizedWeight(ui);
    ui->setAnimationWeight("Die", weight);
}

void DeathState::init(UnitInterpolator *ui)
{
    ui->enableSingleAnimation("Die", true);
}

void DeathState::end(UnitInterpolator *ui){}

UnitInterpolator::UnitInterpolator(sim::Unit::ConstPointer unit, sim::Int playerId, sim::Int squadId,
    const LandscapeSpatials & spatials, Ogre::SceneManager & sceneMan) :
        EntityInterpolator(spatials,
            ENTITY_QUERY_FLAG_UNIT,
            sceneMan,
            unit->getInvariant().getMeshName(),
            unit->getInvariant().getMeshScale(),
            convertPosition(unit->getPosition(),
            spatials),
            convertAngle(unit->getFacing()))
{
    setEntityData("player", Ogre::Any(sim::WideInt(playerId))); //have to be WideInt or Ogre::Any goes bonkers when deserializing
    setEntityData("squad", Ogre::Any(sim::WideInt(squadId)));
}

float UnitInterpolator::stateWeightSum()
{
    return stateMotion.getWeight() + 
           stateFighting.getWeight() +
           stateDeath.getWeight();
}

void UnitInterpolator::gfxUpdate(float deltaTime)
{
    stateMotion.update(deltaTime, this);
    stateFighting.update(deltaTime, this);
    stateDeath.update(deltaTime, this);
    
    if(!stateMotion.getIsActive() && !stateFighting.getIsActive() && !stateDeath.getIsActive())
    {
        stateMotion.enable(this);
    }
    
    rotation.update(deltaTime);
    setYaw((Ogre::Radian)rotation.get());
    
    EntityInterpolator::gfxUpdate(deltaTime);
}

void UnitInterpolator::onUnitUpdate(sim::Unit::ConstPointer unit)
{
    Ogre::Radian newFacing = convertAngle(unit->getFacing());
    newFacing = -newFacing + Ogre::Radian(unit->getInvariant().getMeshYaw());
    rotation.setPeriodical(newFacing.valueRadians(), (float)OGRE_PI * 2);
    
    Ogre::Vector3 newPosition = convertPosition(unit->getPosition(), getSpatials());
    speed = (newPosition - getPosition()).length() + fabs(rotation.getDelta()) * 0.1f;
    setPosition(newPosition);
     

    if(unit->isFighting() || unit->isHarrassing()) {
        if(unit->startedAttack()){
            stateMotion.disable();
            stateFighting.enable(this);
        }
    }
    else if(unit->isDead())
    {
        stateMotion.disable();
        stateFighting.disable();
        stateDeath.enable(this);
    }
    else if(unit->isIdle() || unit->isFleeing())
    {
        stateMotion.enable(this);
        stateFighting.disable();
    }
    
    stateMotion.stateUpdate(this, unit);
    stateFighting.stateUpdate(this, unit);
    stateDeath.stateUpdate(this, unit);
}

}; //presentation
}; //spelprog2
